#ifndef FILE_HELPER_H_
#define FILE_HELPER_H_
#include <Eigen/Dense>
#include <boost/json.hpp>
#include <filesystem>
#include <fstream>
#include <functional>
#include <memory>
#include <vector>

namespace autodrive {
namespace perception {

inline std::streampos get_binfile_size(std::string path) noexcept {
  std::streampos fileSize;
  std::ifstream stream{path};
  stream.seekg(0, stream.end);
  fileSize = stream.tellg();
  stream.close();
  return fileSize;
}

inline void read_bin_file(std::string path, char* buf, int size) noexcept {
  std::ifstream stream{path};
  stream.read(buf, size);
  stream.close();
}

inline void write_bin_file(std::string path, char* buf, int size) noexcept {
  std::ofstream stream{path};
  stream.write(buf, size);
  stream.close();
}

template <typename T>
inline void read_bin_file(std::string path, std::vector<T>& data) noexcept {
  auto binSize{get_binfile_size(path)};
  auto vectorSize{binSize / sizeof(T)};
  data.resize(vectorSize);
  read_bin_file(path, reinterpret_cast<char*>(data.data()), binSize);
}

inline void write_json(std::string const& filePath,
                       std::string const& strJson) noexcept {
  std::ofstream stream{filePath};
  stream << strJson;
  stream.close();
}

template <typename T>
std::string serialize_json(T const& obj) noexcept {
  return boost::json::serialize(boost::json::value_from(obj));
}

template <typename T>
T deserialize_obj(boost::json::object const& obj,
                  std::string const& name) noexcept(false) {
  if (obj.contains(name)) {
    return boost::json::value_to<T>(obj.at(name));
  } else {
    throw std::runtime_error("no " + name + " found in json object");
  }
}

/// @brief
/// @param path
/// @return
std::string read_text(std::filesystem::path const& path) noexcept(false);

/// @brief
/// @tparam T
/// @param j_str
/// @return
template <typename T>
T json_deserialize(std::string const& j_str) noexcept(false) {
  boost::json::value jv;
  try {
    boost::json::error_code ec;
    jv = boost::json::parse(j_str, ec);
    if (ec) {
      std::string err{"[Throw] parse json string fail: " + ec.message()};
      throw std::runtime_error(err);
    }
    return boost::json::value_to<T>(jv);
  } catch (std::exception const& e) {
    std::string err{"[Throw] parse json string fail:" + std::string(e.what())};
    throw std::runtime_error(err);
  } catch (...) {
    throw std::runtime_error("[Throw] parse json string fail, unexpected!!!");
  }
}

inline void write_matrix(
    Eigen::MatrixXd const& matrix,
    std::filesystem::path const& file_path) noexcept(false) {
  Eigen::IOFormat txtFormat(Eigen::FullPrecision, Eigen::DontAlignCols, " ",
                            "\n");
  std::ofstream file(file_path);
  if (!file.is_open()) {
    throw std::runtime_error("file " + file_path.string() + " can not open");
  }
  file << matrix.format(txtFormat);
  file.close();
}

/// @brief read text file
/// @tparam T data type
/// @param path file path
/// @param obj data object
template <typename T>
void read_data(std::filesystem::path const& path,
               std::vector<T>& obj) noexcept(false) {
  static_assert(std::is_arithmetic_v<T>,
                "read text type is not arithmetic type");
  if (!std::filesystem::exists(path)) {
    throw std::runtime_error("file " + path.string() + " does not exist");
  }
  std::ifstream iStream{path};
  if (!iStream.is_open() || !iStream.good()) {
    throw std::runtime_error("failed to open file " + path.string());
  }
  obj = {std::istream_iterator<T>(iStream), std::istream_iterator<T>()};
}
}  // namespace perception
}  // namespace autodrive

#endif